/*
 * tcp_listen.c
 *
 *  Created on: 2015-5-15
 *      Author: likui
 */

#include "tcp_listen.h"
#include "error.h"

#include <string.h>
#include <unistd.h>
#include <netdb.h>
#include <sys/socket.h>

/**
 * @brief 创建一个TCP套接字，给它捆绑服务器众所周知的端口，并允许接受外来的连接请求
 * @param host 主机名或或IP地址
 * @param serv 服务名或端口号
 * @param addrlenp 通过指针返回地址长度信息
 * @return 若成功返回监听套接字，若失败终止程序
 */
int tcp_listen(const char *host, const char *serv, struct sockaddr *addr, socklen_t *addrlenp)
{
	int				listenfd, n;
	const int		on = 1;
	struct addrinfo	hints, *res, *ressave;

	memset(&hints, '\0', sizeof(hints));
	hints.ai_flags = AI_PASSIVE;		// 套接字用于被动打开
	hints.ai_family = AF_UNSPEC;		// IPv4 or IPv6
	hints.ai_socktype = SOCK_STREAM;	// TCP

	// 获取地址列表
	if ( (n = getaddrinfo(host, serv, &hints, &res)) != 0) {
		error_quit("mini_listen error for %s, %s: %s",
				host, serv, gai_strerror(n));
	}

	ressave = res;

	// 遍历地址
	do {
		// 创建套接字
		listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);

		if (listenfd < 0)
			continue;

		// 允许在同一端口上启动同一的多个实例，只要每个实例捆绑一个不同的本地IP地址即可
		if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) {
			error_sys("setsockopt error");
		}

		// 将套接字和套接字地址结构绑定
		if (bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
			break;							// 若成功则跳出循环

		// 关闭套接字
		if (close(listenfd) < 0) {
			error_sys("close error");
		}

	}while ( (res = res->ai_next) != NULL);

	if (res == NULL)
		error_sys("mini_listen error for %s, %s", host, serv);

	// 将套接字转为监听状态
	if (listen(listenfd, LISTENQ) < 0) {
		error_sys("listen error");
	}

	// 通过指针传回地址长度信息
	if (addrlenp)
		*addrlenp = res->ai_addrlen;
	if (addr) {
		memcpy(addr, res->ai_addr, res->ai_addrlen);
	}

	// 释放addrinfo 结构
	freeaddrinfo(ressave);

	return listenfd;
}
